/******************************************************************************
 * @file     runtime_test.c
 * @version  V0.10
 * $Revision: 1 $
 * $Date: 21/02/23 9:23a $
 * @brief    IEC60730 Runtime Test
 * @note
 * SPDX-License-Identifier: Apache-2.0
 * Copyright (C) 2016-2020 Nuvoton Technology Corp. All rights reserved.
 ******************************************************************************/

#include <stdio.h>
#include <string.h>
#include "nuc980.h"


#include "IEC60730_CONTROL_PARAM.h"
#include "IEC60730_SAFETY_TEST_LIB.h"


#ifdef __ARMCC_VERSION
extern uint32_t Image$$RW_STACK$$Base;
extern uint32_t Image$$RW_STACK$$ZI$$Length;
extern __asm void Disable_AllException(void);
extern __asm void Enable_AllException(void);
#endif

#ifdef __ICCARM__
extern uint32_t CSTACK$$Base;
extern uint32_t CSTACK$$Limit;
#endif


/* variables declaration */
extern uint8_t volatile u8CPUTestPass;
extern uint8_t volatile u8PCTestPass;
extern uint8_t volatile u8IntTestPass;
extern uint8_t volatile u8StackTestPass;
extern uint8_t volatile u8RAMTestPass;
extern uint8_t volatile u8DRAMTestPass;
extern uint8_t volatile u8ROMTestPass;
uint8_t volatile u8RAMParityTestPass;

extern uint32_t u32SysTickCnt;
extern uint32_t u32Timer0Cnt;
volatile uint32_t u32Timer5Cnt;
volatile uint32_t u32TimerXCnt, u32TimerYCnt;
extern uint32_t u32TimerRatio;


uint32_t u32CPUTestOnce;
uint32_t u32PCTestOnce;
uint32_t u32StackTestOnce;
uint32_t u32RomTestOnce;
uint32_t u32RamTestOnce;
uint32_t u32DramTestOnce;
uint32_t u32IntTestOnce;
uint32_t u32RestWdtOnce;
uint32_t u32timercnt_old;
uint32_t volatile g_u32RunTimeInit;

/* for stack runtime test */
extern volatile uint32_t* g_pu32StackPtrn;
extern uint32_t const STACK_TEST_PTRN[];


/* for wdt startup test */
uint32_t volatile u32WdtInt;


/* please don't change "g_pu32StackPtrn" in other functions */
static void GetStackPtrnPtr(void)
{
    asm volatile
    (
        "push    {r0-r7}  \n"
        "ldr     r1, [%0] \n"
        "mov     r0, sp \n"
        "ldr     r2, =0x4000 \n"        /* 0x20: r0-r7 */
        "sub     r0, r0, r2 \n"             /* 0x18: SP-0x18 when enters this function*/
        "ldr     r2, =(0x20+0x18-0x10)  \n" /* 0x10: stack pattern area size*/
        "add     r0, r0, r2 \n"
        "str     r0, [r1] \n"
        "pop     {r0-r7} \n"
        "bx      lr  \n"
        :"=r"(g_pu32StackPtrn)
        :
        :
    );
}

static void CopyStackPtrn(void)
{
    uint32_t *pu32PtrnSrc, *pu32PtrnDest;

    /* get stored stack pattern pointer
        and saved in "g_pu32StackPtrn" */
    GetStackPtrnPtr();

    pu32PtrnSrc = (uint32_t*) STACK_TEST_PTRN;
    pu32PtrnDest = (uint32_t *)g_pu32StackPtrn;
    *pu32PtrnDest++ = *pu32PtrnSrc++;
    *pu32PtrnDest++ = *pu32PtrnSrc++;
    *pu32PtrnDest++ = *pu32PtrnSrc++;
    *pu32PtrnDest++ = *pu32PtrnSrc++;
}

/*-------------------------------------------*/
/*                wdt isr                    */
/*-------------------------------------------*/
/////void WDT_IRQHandler()
static void WDT_IRQHandler()
{
    /* for wdt startup test */
    SYS_UnlockReg();
    WDT_RESET_COUNTER();
    WDT_CLEAR_TIMEOUT_INT_FLAG();
    u32WdtInt = TRUE;
//    SYS_LockReg();
}

/*-------------------------------------------*/
/*                timerX isr                 */
/*-------------------------------------------*/
////void ETMRX_IRQHandler(void)
static void ETMRX_IRQHandler(void)
{
    /* clear timerX interrupt flag */
    ETIMER_ClearIntFlag(TIMERX);

    /* increase timer count */
    u32TimerXCnt++;
}

/*-------------------------------------------*/
/*                timerY isr                 */
/*-------------------------------------------*/
////void ETMRY_IRQHandler(void)
static void ETMRY_IRQHandler(void)
{
    /* clear timerY interrupt flag */
    ETIMER_ClearIntFlag(TIMERY);

    /* increase timer count */
    u32TimerYCnt++;
}
/*-------------------------------------------*/
/*                init wdt                   */
/*-------------------------------------------*/
static void init_wdt(void)
{
    /* unlock register */
    SYS_UnlockReg();

    /* select WDT clock source from clk_in/512 (12 MHz/512) */
    outpw(REG_CLK_DIVCTL8, (inpw(REG_CLK_DIVCTL8) & ~0x300) | 0x100);

    /* enable WDT engine clock */
    outpw(REG_CLK_PCLKEN0, inpw(REG_CLK_PCLKEN0) | 1);

    /* clear WDT reset flag */
    WDT_CLEAR_RESET_FLAG();

    /* reset counter */
    WDT_RESET_COUNTER();

    /* Configure WDT settings and start WDT counting, Set WDT time out interval
       to 2^14 Twdt = 0.7 sec. Where Twdt = 1/(12MHZ/512) */
    WDT_Open(WDT_TIMEOUT_2POW14, WDT_RESET_DELAY_18CLK, TRUE, FALSE);   // enable RESET
//    WDT_Open(WDT_TIMEOUT_2POW14, WDT_RESET_DELAY_18CLK, FALSE, FALSE);    // disable RESET

    /* clear WDT timeout interrupt flag */
//  WDT_CLEAR_TIMEOUT_INT_FLAG();

    /* enable WDT_INT */
//  WDT_EnableInt();

    /* lock register */
    SYS_LockReg();
}

/*-------------------------------------------*/
/*              reset wdt counter            */
/*-------------------------------------------*/
static void reset_wdt(void)
{
    /* reset wdt counter and time out flag */
    SYS_UnlockReg();
    WDT_RESET_COUNTER();
    WDT_CLEAR_TIMEOUT_INT_FLAG();
    SYS_LockReg();
}

/*-------------------------------------------*/
/*                init timerX                */
/*-------------------------------------------*/
static void InitTimerX(void)
{
    /* select timerX clock from clk_in (12 MHz) */
    outpw(REG_CLK_DIVCTL8, inpw(REG_CLK_DIVCTL8) & ~(0x3<<(16+TIMERX*2)));

    /* enable ETIMERX engine clock */
    outpw(REG_CLK_PCLKEN0, inpw(REG_CLK_PCLKEN0) | (1 << (8+TIMERX)));

    /* set timerX frequency (Hz) */
    ETIMER_Open(TIMERX, ETIMER_PERIODIC_MODE, TIMERX_RUNTIME_FREQ);
}

/*-------------------------------------------*/
/*                init timerY                */
/*-------------------------------------------*/
static void InitTimerY(void)
{
#ifdef TIMERY_RUNTIME_FROM_32K
    /* enable 32K clock input */
    outpw(REG_CLK_PCLKEN0, inpw(REG_CLK_PCLKEN0) | BIT2);
    RTC->INIT = RTC_INIT_KEY;
    while(!((RTC->INIT) & 0x01));
    RTC_WaitAccessEnable();
    outpw(REG_RTC_CLKCTL, inpw(REG_RTC_CLKCTL) | BIT0);
    RTC_Check();

    /* select timerY clock from 32k */
    outpw(REG_CLK_DIVCTL8, inpw(REG_CLK_DIVCTL8) | (0x3<<(16+TIMERY*2)));
#else
    /* select timerY clock from PCLK */
    outpw(REG_CLK_DIVCTL8, (inpw(REG_CLK_DIVCTL8)
                            & ~(0x3<<(16+TIMERY*2))) | (0x1<<(16+TIMERY*2)));
#endif
    /* enable ETIMERY engine clock */
    outpw(REG_CLK_PCLKEN0, inpw(REG_CLK_PCLKEN0) | (1 << (8+TIMERY)));

    /* set timerX frequency (Hz) */
    ETIMER_Open(TIMERY, ETIMER_PERIODIC_MODE, TIMERY_RUNTIME_FREQ);
}

void IEC60730_DoRunTimeIntial(void)
{
    /* init timerX */
    InitTimerX();
    u32TimerXCnt = 0;

    /* setup timerX interrupt */
    ETIMER_ClearIntFlag(TIMERX);
    sysInstallISR(IRQ_LEVEL_1, IRQ_TIMERX, (PVOID)ETMRX_IRQHandler);
    ETIMER_EnableInt(TIMERX);
    sysEnableInterrupt(IRQ_TIMERX);

    /* init timerY */
    InitTimerY();
    u32TimerYCnt = 0;

    /* setup timerY interrupt */
    ETIMER_ClearIntFlag(TIMERY);
    sysInstallISR(IRQ_LEVEL_1, IRQ_TIMERY, (PVOID)ETMRY_IRQHandler);
    ETIMER_EnableInt(TIMERY);
    sysEnableInterrupt(IRQ_TIMERY);

    /* enable global interrupt */
    sysSetLocalInterrupt(ENABLE_IRQ);

    /* start timer0 and timer5 */
    ETIMER_Start(TIMERX);
    ETIMER_Start(TIMERY);

    /* init and start wdt */
    init_wdt();

    /* copy stack overrun pattern to protected area */
    CopyStackPtrn();

    /* init related variables */
    u32CPUTestOnce = 0;
    u32PCTestOnce = 0;
    u32StackTestOnce = 0;
    u32RomTestOnce = 0;
    u32RamTestOnce = 0;
    u32DramTestOnce = 0;
    u32IntTestOnce = 0;
    u32RestWdtOnce = 0;
    u32timercnt_old = 0;
    g_u32RunTimeInit = 0;
}

void IEC60730_DoRunTimeChecks(void)
{
    uint32_t* ptimercnt;
    uint32_t  timercnt;

    ptimercnt = (uint32_t*)&u32TimerXCnt;

    /* get timer count */
    timercnt = *ptimercnt;

    /* CPU Registers Test */
    if (!(timercnt%CPUREG_TEST_CYCLE))
    {
        if (!u32CPUTestOnce)
        {
            u32CPUTestOnce = 1;
            u8CPUTestPass = 0;
            u8CPUTestPass = IEC60730_CPU_Reg_Test_RunTime();
            if(u8CPUTestPass)
                rt_printf("runtime CPU reg test: Pass !!\n");
            else
            {
                rt_printf("runtime CPU reg test: Fail !!\n");
                while(1);
            }
        }
    }
    /* Program Counter Test */
    if (!(timercnt%PC_TEST_CYCLE))
    {
        if (!u32PCTestOnce)
        {
            u32PCTestOnce = 1;
            u8PCTestPass = IEC60730_PC_Test_RunTime();
            if(u8PCTestPass)
                rt_printf("runtime PC test: Pass !!\n");
            else
            {
                rt_printf("runtime PC test: Fail !!\n");
                while(1);
            }
        }
    }

    /* Stack Overrun Test */
    if (!(timercnt%STACK_TEST_CYCLE))
    {
        if (!u32StackTestOnce)
        {
            u32StackTestOnce = 1;
            u8StackTestPass = IEC60730_Stack_Test_RunTime();
            if(u8StackTestPass)
                rt_printf("runtime Stack test: Pass !!\n");
            else
            {
                rt_printf("runtime Stack test: Fail !!\n");
                while(1);
            }
        }
    }

    /* RAM Test */
    if (!(timercnt%RAM_TEST_CYCLE))
    {
        if (!u32RamTestOnce)
        {
            u32RamTestOnce = 1;
#ifdef RAM_RUNTIME_MARCHC_WOM
            u8RAMTestPass = IEC60730_RAM_MarchC_Test_RunTime();
#endif
#ifdef RAM_RUNTIME_MARCHX_WOM
            u8RAMTestPass = IEC60730_RAM_MarchX_Test_RunTime();
#endif
            if(u8RAMTestPass)
                rt_printf("runtime RAM test: Pass !!\n");
            else
            {
                rt_printf("runtime RAM test: Fail !!\n");
                while(1);
            }
        }
    }

    /* DRAM Test */
    if (!(timercnt%DRAM_TEST_CYCLE))
    {
        if (!u32DramTestOnce)
        {
            u32DramTestOnce = 1;
#ifdef DRAM_RUNTIME_MARCHC_WOM
            u8DRAMTestPass = IEC60730_DRAM_MarchC_Test_RunTime();
#endif
#ifdef DRAM_RUNTIME_MARCHX_WOM
            u8DRAMTestPass = IEC60730_DRAM_MarchX_Test_RunTime();
#endif
            if(u8DRAMTestPass)
                rt_printf("runtime DRAM test: Pass !!\n");
            else
            {
                rt_printf("runtime DRAM test: Fail !!\n");
                while(1);
            }
        }
    }

    /* ROM Test */
    if (!(timercnt%ROM_TEST_CYCLE))
    {
        if (!u32RomTestOnce)
        {
            u32RomTestOnce = 1;
            u8ROMTestPass = IEC60730_Flash_Test_RunTime();
            if(u8ROMTestPass)
                rt_printf("runtime ROM test: Pass !!\n");
            else
            {
                rt_printf("runtime ROM test: Fail !!\n");
                while(1);
            }
        }
    }

    /* Interrupt Test */
    if (!(timercnt%INT_TEST_CYCLE))
    {
        if (!u32IntTestOnce)
        {
            u32IntTestOnce = 1;
            u8IntTestPass = IEC60730_Interrupt_Test_RunTime();
            if(u8IntTestPass)
                rt_printf("runtime Interrupt test: Pass !!\n");
            else
            {
                rt_printf("runtime Interrupt test: Fail !!\n");
                while(1);
            }
        }
    }

    /* reset wdt */
    if (!(timercnt%REST_WDT_CYCLE))
    {
        if (!u32RestWdtOnce)
        {
            u32RestWdtOnce = 1;
            reset_wdt();
        }
    }

    /* check whether counter has been updated or not */
    if (u32timercnt_old != timercnt)
    {
        u32timercnt_old = timercnt;
        u32CPUTestOnce = 0;
        u32PCTestOnce = 0;
        u32StackTestOnce = 0;
        u32RomTestOnce = 0;
        u32RamTestOnce = 0;
        u32IntTestOnce = 0;
        u32RestWdtOnce = 0;
        u32DramTestOnce = 0;
    }

    /* setup maximum counter value */
    if (timercnt >= 0x000FFFFF)
    {
        u32SysTickCnt = 0;
        u32Timer0Cnt = 0;
    }
}


/*** (C) COPYRIGHT 2013 Nuvoton Technology Corp. ***/
